package com.alibaba.smart.framework.engine.behavior.base;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.alibaba.smart.framework.engine.behavior.ActivityBehavior;
import com.alibaba.smart.framework.engine.bpmn.assembly.gateway.AbstractGateway;
import com.alibaba.smart.framework.engine.bpmn.behavior.gateway.helper.CommonGatewayHelper;
import com.alibaba.smart.framework.engine.common.util.InstanceUtil;
import com.alibaba.smart.framework.engine.common.util.MapUtil;
import com.alibaba.smart.framework.engine.common.util.MarkDoneUtil;
import com.alibaba.smart.framework.engine.configuration.ProcessEngineConfiguration;
import com.alibaba.smart.framework.engine.context.ExecutionContext;
import com.alibaba.smart.framework.engine.exception.EngineException;
import com.alibaba.smart.framework.engine.instance.factory.ActivityInstanceFactory;
import com.alibaba.smart.framework.engine.instance.factory.ExecutionInstanceFactory;
import com.alibaba.smart.framework.engine.instance.factory.ProcessInstanceFactory;
import com.alibaba.smart.framework.engine.instance.factory.TaskInstanceFactory;
import com.alibaba.smart.framework.engine.instance.storage.ExecutionInstanceStorage;
import com.alibaba.smart.framework.engine.instance.storage.VariableInstanceStorage;
import com.alibaba.smart.framework.engine.model.assembly.Activity;
import com.alibaba.smart.framework.engine.model.assembly.IdBasedElement;
import com.alibaba.smart.framework.engine.model.instance.ActivityInstance;
import com.alibaba.smart.framework.engine.model.instance.ExecutionInstance;
import com.alibaba.smart.framework.engine.model.instance.ProcessInstance;
import com.alibaba.smart.framework.engine.pvm.PvmActivity;
import com.alibaba.smart.framework.engine.pvm.PvmTransition;
import com.alibaba.smart.framework.engine.pvm.event.EventConstant;

import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author 高海军 帝奇  2016.11.11
 * @author ettear 2016.04.13
 */
public abstract class AbstractActivityBehavior<T extends Activity> implements ActivityBehavior {


  @Setter
  protected ProcessInstanceFactory processInstanceFactory;
  @Setter
  protected ExecutionInstanceFactory executionInstanceFactory;
  @Setter
  protected ActivityInstanceFactory activityInstanceFactory;
  @Setter
  protected TaskInstanceFactory taskInstanceFactory;
  @Setter
  protected ProcessEngineConfiguration processEngineConfiguration;
  @Setter
  protected ExecutionInstanceStorage executionInstanceStorage;

  @Setter
  protected VariableInstanceStorage variableInstanceStorage;

  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractActivityBehavior.class);

  protected void fireEvent(ExecutionContext context, PvmActivity pvmActivity, EventConstant event) {

    context.getProcessEngineConfiguration().getListenerExecutor().execute(event, pvmActivity.getModel(), context);

  }

  @Override
  public boolean enter(ExecutionContext context, PvmActivity pvmActivity) {

    ProcessInstance processInstance = context.getProcessInstance();

    synchronized (processInstance) {

      ActivityInstance activityInstance = createSingleActivityInstanceAndAttachToProcessInstance(context, pvmActivity.getModel());

      ExecutionInstance executionInstance = this.executionInstanceFactory.create(activityInstance, context);

      List<ExecutionInstance> executionInstanceList = new ArrayList<ExecutionInstance>(1);
      executionInstanceList.add(executionInstance);

      activityInstance.setExecutionInstanceList(executionInstanceList);
      context.setExecutionInstance(executionInstance);

      IdBasedElement idBasedElement = context.getProcessDefinition().getIdBasedElementMap().get(executionInstance.getProcessDefinitionActivityId());
      context.setBaseElement(idBasedElement);

      fireEvent(context, pvmActivity, EventConstant.ACTIVITY_START);

      hookEnter(context, pvmActivity);

      return false;
    }

  }

  protected void hookEnter(ExecutionContext context, PvmActivity pvmActivity) {
    // default: do nothing

    // 意图: 目前仅包容网关需要，在首次生成包容网关 fork EI 的时候，需要将instanceId 赋值到 context 里面去

  }

  protected ActivityInstance createSingleActivityInstanceAndAttachToProcessInstance(ExecutionContext context, Activity activity) {
    ProcessInstance processInstance = context.getProcessInstance();

    ActivityInstance activityInstance = this.activityInstanceFactory.create(activity, context);
    processInstance.addActivityInstance(activityInstance);
    context.setActivityInstance(activityInstance);

    return activityInstance;
  }


  @Override
  public void execute(ExecutionContext context, PvmActivity pvmActivity) {

    hookExecute(context, pvmActivity);
    if (context.isNeedPause()) {
      // break;
      return;
    }

    fireEvent(context, pvmActivity, EventConstant.ACTIVITY_EXECUTE);

    executeDelegation(context, pvmActivity.getModel());

    commonUpdateExecutionInstance(context);

  }

  protected void hookExecute(ExecutionContext context, PvmActivity pvmActivity) {
    // default: do nothing

    // 意图: 用于解决页面上多个按钮,当用户点击了这个按钮,却不触发业务流程的状态的变化,仅仅是改变业务领域对象.
    // 所以,这里提供了一个扩展机制,允许ClientProgrammer 在仍然使用signal 方法的时候,但是不驱动流程变化. 一般业务忽略此方法即可.
    // 一般示例: 传入 开启选项参数 和 按钮名称, 然后反射调用对应的类执行业务逻辑
  }


  protected void commonUpdateExecutionInstance(ExecutionContext context) {
    if (!context.isNeedPause()) {
      ExecutionInstance executionInstance = context.getExecutionInstance();
      MarkDoneUtil.markDoneExecutionInstance(executionInstance, executionInstanceStorage,
          processEngineConfiguration);
    }
  }

  protected void executeDelegation(ExecutionContext context, Activity activity) {

    context.getProcessEngineConfiguration().getDelegationExecutor().execute(context, activity);
  }


  /**
   * dead or alive ,maybe change later...
   *
   * @param context
   * @param gateway
   * @param processInstance
   * @param activeExecutionList
   * @param countOfTheJoinLatch
   * @param forkedExecutionInstanceOfInclusiveGateway
   * @return
   */
  protected boolean doa(ExecutionContext context, AbstractGateway gateway, ProcessInstance processInstance, List<ExecutionInstance> activeExecutionList, int countOfTheJoinLatch, ExecutionInstance forkedExecutionInstanceOfInclusiveGateway) {
    List<ExecutionInstance> executionInstanceListFromMemory = InstanceUtil.findActiveExecution(processInstance);

    List<ExecutionInstance> mergedExecutionInstanceList = new ArrayList<ExecutionInstance>();

    mergedExecutionInstanceList.addAll(executionInstanceListFromMemory);

    for (ExecutionInstance executionInstance : activeExecutionList) {
      if (mergedExecutionInstanceList.contains(executionInstance)) {
        //do nothing
      } else {
        mergedExecutionInstanceList.add(executionInstance);
      }
    }

    int reachedJoinCounter = 0;
    List<ExecutionInstance> chosenExecutionInstanceList = new ArrayList<ExecutionInstance>(executionInstanceListFromMemory.size());

    if (null != mergedExecutionInstanceList) {

      for (ExecutionInstance executionInstance : mergedExecutionInstanceList) {

        if (executionInstance.getProcessDefinitionActivityId().equals(gateway.getId())) {
          reachedJoinCounter++;
          chosenExecutionInstanceList.add(executionInstance);
        }
      }
    }


    LOGGER.debug("chosenExecutionInstanceList , reachedJoinCounter,countOfTheJoinLatch  is {} , {} , {} ", chosenExecutionInstanceList, reachedJoinCounter, countOfTheJoinLatch);
    if (reachedJoinCounter > countOfTheJoinLatch) {
      throw new EngineException("Unexpected behavior,reachedJoinCounter: " + reachedJoinCounter + ",countOfTheJoinLatch: " + countOfTheJoinLatch);
    } else if (reachedJoinCounter == countOfTheJoinLatch) {
      //把当前停留在join节点的执行实例全部complete掉,然后再持久化时,会自动忽略掉这些节点。

      if (null != chosenExecutionInstanceList) {
        for (ExecutionInstance executionInstance : chosenExecutionInstanceList) {
          MarkDoneUtil.markDoneExecutionInstance(executionInstance, executionInstanceStorage,
              processEngineConfiguration);
        }
      }

      // 虽然 fork-join 对已经结束,但是 unbalanced 这种场景还需要 blockId 去计算
      // context.setBlockId(null);

      hookCleanUp(context, forkedExecutionInstanceOfInclusiveGateway);

      return false;

    } else {
      //未完成的话,流程继续暂停
      return true;
    }
  }

  protected void hookCleanUp(ExecutionContext context, ExecutionInstance forkedExecutionInstanceOfInclusiveGateway) {

  }


  @Override
  public void leave(ExecutionContext context, PvmActivity pvmActivity) {
    fireEvent(context, pvmActivity, EventConstant.ACTIVITY_END);


    Map<String, PvmTransition> outcomeTransitions = pvmActivity.getOutcomeTransitions();

    if (MapUtil.isEmpty(outcomeTransitions)) {
      LOGGER.debug("No outcomeTransitions found for activity id: " + pvmActivity.getModel().getId() + ", it's just fine, it should be the last activity of the process");

    } else {

      if (outcomeTransitions.size() == 1) {
        for (Entry<String, PvmTransition> pvmTransitionEntry : outcomeTransitions.entrySet()) {
          PvmActivity target = pvmTransitionEntry.getValue().getTarget();
          target.enter(context);
        }
      } else {

        CommonGatewayHelper.chooseOnlyOne(pvmActivity, context);

      }
    }

  }


}
